﻿/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-19
  File:    GameServer.cs  
  Description: 
    Implementation of the game server process.
*/

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SL.M.Net
{
    using Debug;

    /// <summary>
    /// Represent a multiplayer game server.
    /// </summary>
    /// <remarks>After connecting, the following message types are made available.
    /// <para>A <code>us.sl.mgame.serverFrame</code> timer message is periodically fired when the server needs to update the client sessions.</para>
    /// <para>Users can determine when the server has stopped accepting connections by checking the <code>us.sl.m.stop</code> event.</para>
    /// <see cref="SL.M.Net.GameServerSession"/>
    /// <see cref="SL.M.GameObject"/>
    /// </remarks>
    public class GameServer<T> : Process where T : GameObject
    {
        const string Tag = "server: ";

        internal static readonly MessageType ServerFrameType = "us.sl.mgame.serverFrame";

        IPEndPoint[] _ipEp;
        T _serverState;
        Func<GameObject> _authorizationInitializer;
        Action<GameObject> _authorizationAction;
        int _numClients;
        int _maxClients;
        TimeSpan _serverFrame;
        TimerState _frameTimer;
        SessionFlags _flags;

        /// <summary>
        /// Return or set the current server frame.
        /// </summary>
        public TimeSpan ServerFrame
        {
            get
            {
                return _serverFrame;
            }
            set
            {
                _serverFrame = value;
            }
        }

        /// <summary>
        /// Return the server object for this game.
        /// </summary>
        public T ServerObject
        {
            get
            {
                return _serverState;
            }
        }

        /// <summary>
        /// Return or set the current number of clients.
        /// </summary>
        public int NumClients
        {
            get
            {
                return _numClients;
            }
        }

        /// <summary>
        /// Return the maximum number of clients.
        /// </summary>
        public int MaxClients
        {
            get
            {
                return _maxClients;
            }
        }

        /// <summary>
        /// Return or set the server read timeout.
        /// </summary>
        public TimeSpan ReadTimeout
        {
            get;
            set;
        }

        /// <summary>
        /// Return or set the server write timeout.
        /// </summary>
        public TimeSpan WriteTimeout
        {
            get;
            set;
        }

        /// <summary>
        /// Return or set the server read buffer size.
        /// </summary>
        public int ReadBufferSize
        {
            get;
            set;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="ipEp">The endpoint to listen on.</param>
        /// <param name="serverState">A reference to the server state object to be used for transmitting server frames.</param>
        /// <param name="authorizationInitializer">A functor which initializes the authorization state to be used during the authorization phase for a specified client. The client should send the correct state to authorize itself with the server. If the authorization action fails by throwing an exception, the message will be returned to the client.</param>
        /// <param name="authorizationAction">The authorization action which is used during the authorization phase to determine if the specified game object should be allowed to join the session.</param>
        /// <param name="maxClients">The maximum amount of clients the server can accept.</param>
        /// <param name="flags">The session flags, if any.</param>
        public GameServer(IPEndPoint[] ipEp, T serverState, Func<GameObject> authorizationInitializer, Action<GameObject> authorizationAction, int maxClients, SessionFlags flags)
        {
            if (ipEp == null)
            {
                throw new ArgumentNullException("ipEp");
            }

            if (serverState == null)
            {
                throw new ArgumentNullException("serverState");
            }

            if (authorizationInitializer == null)
            {
                throw new ArgumentNullException("authorizationInitializer");
            }

            if (authorizationAction == null)
            {
                throw new ArgumentNullException("authorizationAction");
            }

            if (maxClients < 0)
            {
                throw new ArgumentOutOfRangeException("maxClients");
            }

            ServerFrame = Properties.Settings.Default.ServerFrame;
            if (System.Diagnostics.Debugger.IsAttached)
            {
                ReadTimeout = WriteTimeout = TimeSpan.MaxValue;
            }
            else
            {
                ReadTimeout = Properties.Settings.Default.ReadTimeout;
                WriteTimeout = Properties.Settings.Default.WriteTimeout;
            }

            ReadBufferSize = Properties.Settings.Default.ReadBufferSize;
            _maxClients = maxClients;
            _ipEp = ipEp;
            _serverState = serverState;
            _authorizationInitializer = authorizationInitializer;
            _authorizationAction = authorizationAction;
            _flags = flags;
        }

        /// <summary>
        /// Process entry point.
        /// </summary>
        protected override void ProcessEntry()
        {
            DebugProvider.Log(DebugProvider.LastHr, Tag + "Starting GameServer");
            _frameTimer = SetPeriodicTimer(ServerFrame, ServerFrame, ServerFrameType);

            TcpListener[] listeners = new TcpListener[_ipEp.Length];
            for (int i = 0; i < listeners.Length; ++i)
            {
                listeners[i] = new TcpListener(_ipEp[i]);
                listeners[i].Start();
            }

            TimeSpan lastFrameTimer = _serverFrame;

            try
            {
                while (Started)
                {
                    if (lastFrameTimer != _serverFrame)
                    {
                        if (_frameTimer != null)
                        {
                            _frameTimer.Clear();
                        }
                        _frameTimer = SetPeriodicTimer(ServerFrame, ServerFrame, ServerFrameType);
                        lastFrameTimer = _serverFrame;
                    }

                    while (NextMessage()) ;

                    foreach (TcpListener listener in listeners)
                    {
                        while (listener.Pending())
                        {
                            try
                            {
                                TcpClient client = listener.AcceptTcpClient();
                                client.ReceiveTimeout = (int)ReadTimeout.TotalMilliseconds;
                                client.SendTimeout = (int)WriteTimeout.TotalMilliseconds;

                                GameServerSession<T> newSession = new GameServerSession<T>(
                                    this,
                                    client,
                                    _authorizationInitializer,
                                    _authorizationAction,
                                    _flags,
                                    _numClients
                                );
                                Game.AddProcess(newSession);
                            }
                            catch (SocketException)
                            {
                                // Ok.
                            }
                        }
                    }
                }
            }
            finally
            {
                for (int i = 0; i < listeners.Length; ++i)
                {
                    listeners[i].Stop();
                }
            }
        }

        /// <summary>
        /// Message handler.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        protected override bool HandleMessage(Message message)
        {
            if (message.Data is GameServerSession<T>)
            {
                if (message.TypeEquals("us.sl.m.start"))
                {
                    ++_numClients;
                    return true;
                }
                else if (message.TypeEquals("us.sl.m.stop"))
                {
                    --_numClients;
                    return true;
                }
            }
            return base.HandleMessage(message);
        }
    }
}
